﻿using Common.DataBase;
using Common.DataBase.Implement;
using Common.DataBase.Interface;
using Common.IO;
using Common.Utils;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;

namespace ScanFileService {
  public partial class MainService : ServiceBase {

    /// <summary>
    /// 排除的分区，在此列表中的驱动器，不做扫描及目录监控处理
    /// </summary>
    List<string> excludeDrivers = new List<string>();

    /// <summary>
    /// 排除的扩展名，在此列表中的扩展名，不做处理
    /// </summary>
    List<string> excludeExtension = new List<string>();

    /// <summary>
    /// 包含的扩展名，只扫找扩展名在此列表中的文件
    /// </summary>
    List<string> includeExtension = new List<string>();

    Dictionary<string, Thread> threads = new Dictionary<string, Thread>();

    /// <summary>
    /// 记录当前要监控或扫描的分区
    /// </summary>
    List<string> diskList = new List<string>();

    private TableTools<FileInfoEx> _tableConvert;

    /// <summary>
    /// 记录当前有变化的文件路径，因为同一个文件可能会同时触发多个文件监控事件，
    /// 如果已经包含在watcherList里，则不再处理第二个事件
    /// </summary>
    private Dictionary<string, WatcherChangeTypes> watcherList = new Dictionary<string, WatcherChangeTypes>();
    private CancellationTokenSource _tokenSource = new CancellationTokenSource();
    private CancellationToken _token;

    System.Timers.Timer aTimer = new System.Timers.Timer();
    System.Timers.Timer checkDBInvalidTimer = new System.Timers.Timer();

    public MainService() {
      InitializeComponent();
      _tableConvert = new TableTools<FileInfoEx>();
      aTimer.Elapsed += new ElapsedEventHandler(TimedEvent);
      checkDBInvalidTimer.Elapsed += CheckDBInvalidTimer_Elapsed;
    }

    protected override void OnContinue() {
      base.OnContinue();
    }

    protected override void OnPause() {
      base.OnPause(); 
    }

    protected override void OnShutdown() {
      OnStop();
      base.OnShutdown();
    }

    protected override void OnStart(string[] args) {
      if (_sqlHelper == null) {
        _sqlHelper = new SQLiteHelper();
      }
      _tokenSource = new CancellationTokenSource();
      _token = _tokenSource.Token;
            
      try {
        string excludeDriverString = ConfigurationManager.AppSettings["excludeDrivers"];
        string excludeExtensionString = ConfigurationManager.AppSettings["excludeExtension"];
        string includeExtensionString = ConfigurationManager.AppSettings["includeExtension"];
        if (!string.IsNullOrWhiteSpace(excludeDriverString)) {
          string[] drivers = excludeDriverString.Split('|');
          foreach (string driver in drivers) {
            if(!excludeDrivers.Contains(driver)) {
              excludeDrivers.Add(driver);
            }            
          }
        }
        if (!string.IsNullOrWhiteSpace(excludeExtensionString)) {
          string[] extennsions = excludeExtensionString.Split('|');
          foreach (string extennsion in extennsions) {
            excludeExtension.Add(extennsion);
          }
        }
        if (!string.IsNullOrWhiteSpace(includeExtensionString)) {
          string[] extennsions = includeExtensionString.Split('|');
          foreach (string extennsion in extennsions) {
            includeExtension.Add(extennsion);
          }
        }
      } catch (Exception exception) {
        _logger.Error(exception);
      }


      #region 定时器事件  
      aTimer.Interval = 5 * 1000;    //配置文件中配置的秒数  
      aTimer.Enabled = true;

      checkDBInvalidTimer.Interval = 60 * 1000;
      checkDBInvalidTimer.Enabled = true;
      #endregion

      ThreadPool.SetMaxThreads(2,2);
      ThreadPool.SetMinThreads(2,2);



    }


    protected override void OnStop() {
      _tokenSource.Cancel();
      aTimer.Enabled = false;
    }

    /// <summary>
    /// 日志处理引用
    /// </summary>
    private Logger _logger = Logger.GetLogger(typeof(MainService));

    /// <summary>
    /// 数据库操作引用
    /// </summary>
    private ISQLHelper _sqlHelper = null;
    
    /// <summary>
    /// 记录分区与，监控对象的关系
    /// </summary>
    private Dictionary<string, FileSystemWatcher> diskWatcherDictionary = new Dictionary<string, FileSystemWatcher>();




    private FileSystemWatcher CreateFileSystemWatcher(string path, string filter = "*") {
      FileSystemWatcher fileSystemWatcher = new FileSystemWatcher();
      fileSystemWatcher.Path = path;
      fileSystemWatcher.Filter = filter;
      fileSystemWatcher.Changed += new FileSystemEventHandler(OnFileSystemWatcherProcess);
      fileSystemWatcher.Created += new FileSystemEventHandler(OnFileSystemWatcherProcess);
      fileSystemWatcher.Deleted += new FileSystemEventHandler(OnFileSystemWatcherProcess);
      fileSystemWatcher.Renamed += new RenamedEventHandler(OnRenamed);
      fileSystemWatcher.EnableRaisingEvents = true;
      fileSystemWatcher.IncludeSubdirectories = true;
      fileSystemWatcher.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastAccess
                             | NotifyFilters.LastWrite | NotifyFilters.Security | NotifyFilters.Size;     
      return fileSystemWatcher;
    }


    private void OnFileSystemWatcherProcess(object source, FileSystemEventArgs e) {
      if (!watcherList.ContainsKey(e.FullPath) && !isSystem(e.FullPath)) {
        watcherList[e.FullPath] = e.ChangeType;
        Task task = new Task(() => updateFile(e, _token));
        task.Start();
      }
    }

    private void OnRenamed(object source, RenamedEventArgs e) { 
      if(!watcherList.ContainsKey(e.FullPath)) {
        watcherList[e.FullPath] = e.ChangeType;
        Task task = new Task(() => updateFile(e, _token));
        task.Start();
      }
    }

    private void updateFile(FileSystemEventArgs e, CancellationToken token) {
      if(token.IsCancellationRequested == false ) {
        if(e.ChangeType == WatcherChangeTypes.Deleted) {
          _sqlHelper.ExecuteNonQuery(deleteFileSQL, CommandType.Text, new SQLiteParameter[] { new SQLiteParameter("@fullPath", e.FullPath) }, false);
        } else if(e.ChangeType == WatcherChangeTypes.Renamed && File.Exists(e.FullPath)) {
          if(!e.FullPath.StartsWith(AppDomain.CurrentDomain.SetupInformation.ApplicationBase)) {
            DbParameter[] dbParameters = ConstructFileSQLParameter(new FileInfoEx(((RenamedEventArgs)e).FullPath));
            _sqlHelper.ExecuteNonQuery(insertFileSQL, CommandType.Text, dbParameters, false);
            _sqlHelper.ExecuteNonQuery(deleteFileSQL, CommandType.Text, new SQLiteParameter[] { new SQLiteParameter("@fullPath", ((RenamedEventArgs)e).OldFullPath) }, false);
          }
        } else if(File.Exists(e.FullPath)) {
          if(!e.FullPath.StartsWith(AppDomain.CurrentDomain.SetupInformation.ApplicationBase)) {
            DbParameter[] dbParameters = ConstructFileSQLParameter(new FileInfoEx(e.FullPath));
            _sqlHelper.ExecuteNonQuery(insertFileSQL, CommandType.Text, dbParameters, false);
          }          
        }
      }
      watcherList.Remove(e.FullPath);
    }


    /// <summary>
    /// 扫描驱动器（分区）的次数 
    /// </summary>
    private Int64 scanTimesCount = 0;

    private void TimedEvent(object source, ElapsedEventArgs e) {
      try {
        List<string> drivers = new List<string>();
        DriveInfo[] allDirves = DriveInfo.GetDrives();
        foreach (DriveInfo item in allDirves) {
          if (item.DriveType == DriveType.CDRom //跳过光驱
            || item.DriveType == DriveType.Unknown //跳过未知设备
            || excludeDrivers.Contains(item.Name)) {//跳过配置文件中，指定的排除分区
            continue;
          }
          string volumeName = item.Name;
          drivers.Add(volumeName);
          DbParameter[] dbParameters = ConstructDriverSQLParameter(item);
          if(_sqlHelper.ExecuteNonQuery(updateDriverSQL, CommandType.Text, dbParameters, false) == 0) {//更新文件信息，如果返回受凡响条数为0，数据库不存在数据，则插入数据
            _sqlHelper.ExecuteNonQuery(insertDriverSQL, CommandType.Text, dbParameters, false);
          }
        }
        var addDrivers = drivers.Except(diskList);//获取新添加的设备，
        foreach (string driver in addDrivers) {//为新添加设备创建(或开启)监控
          if (diskWatcherDictionary.ContainsKey(driver)) {
            if (diskWatcherDictionary[driver].EnableRaisingEvents == true) {
              continue;
            }
            diskWatcherDictionary[driver].EnableRaisingEvents = true;
          } else {
            diskWatcherDictionary[driver] = CreateFileSystemWatcher(driver);
          }
          ThreadPool.QueueUserWorkItem(new WaitCallback(ScanDriver), driver);
          _logger.Info(string.Format("发现设备【{0}】", driver));
        }
        var removedDrivers = diskList.Except(drivers);//获取卸载设备
        foreach (string driver in removedDrivers) {//关闭卸载设备的监控
          if (diskWatcherDictionary.ContainsKey(driver)) {
            if (diskWatcherDictionary[driver].EnableRaisingEvents == true) {
              diskWatcherDictionary[driver].EnableRaisingEvents = false;
              _logger.Info(string.Format("移除设备【{0}】", driver));
            }
          }
        }
        _sqlHelper.ExecuteNonQuery(deleteDriverSQL, CommandType.Text, new SQLiteParameter[] { new SQLiteParameter("@scanTimesCount", scanTimesCount) }, false);
      } catch (Exception ex) {
        _logger.Error(ex);
      }
      scanTimesCount++;
    }

    /// <summary>
    /// 定时扫描文件是否还存在，或者修改过
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void CheckDBInvalidTimer_Elapsed(object sender, ElapsedEventArgs e) {
      Task task = new Task(() => scanDBFileMethod(), _token);
      task.Start();
    }

    /// <summary>
    /// 每次取时间最早的500条
    /// </summary>
    private void scanDBFileMethod() {
      DataTable filesTable = _sqlHelper.ExecuteQuery(queryEarliestDataSQL);
      List<FileInfoEx> fileList = _tableConvert.GetListFromTable(filesTable);
      foreach(FileInfoEx fileEx in fileList) {
        if(!File.Exists(fileEx.FullPath)) {
          //_sqlHelper.ExecuteNonQuery(deleteFileSQL, CommandType.Text, new SQLiteParameter[] { new SQLiteParameter("@fullPath", fileEx.FullPath) },false);
        }
      }
    }

    private void ScanDriver(object driver) {
      string path = driver as string;
      if(Directory.Exists(path)) {
        ScanFolder(new DirectoryInfo(path));
      }
    }

    private void ScanFolder(DirectoryInfo directory) {
      DirectorySecurity directorySecurity = new DirectorySecurity(directory.FullName, AccessControlSections.Access);
      if(!directorySecurity.AreAccessRulesProtected && directory.FullName != AppDomain.CurrentDomain.SetupInformation.ApplicationBase) {
        FileInfo[] files = directory.GetFiles();
        foreach(FileInfo file in files) {
          try {
            DbParameter[] dbParameters = ConstructFileSQLParameter(new FileInfoEx(file));
            if(_sqlHelper.ExecuteNonQuery(updateFileSQL, CommandType.Text, dbParameters, false) == 0) {//更新文件信息，如果返回受凡响条数为0，数据库不存在数据，则插入数据
              _sqlHelper.ExecuteNonQuery(insertFileSQL, CommandType.Text, dbParameters, false);
            }
          } catch(Exception exception) {
            _logger.Error(exception);
          }
        }

        DirectoryInfo[] directorys = directory.GetDirectories();
        foreach(DirectoryInfo childDirectory in directorys) {
          if((childDirectory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden || (childDirectory.Attributes & FileAttributes.System) == FileAttributes.System) {//系统和隐藏的文件夹
            continue;
          }
          try {
            ScanFolder(childDirectory);
          } catch(Exception exception) {
            _logger.Error(exception);
          }
        }
      }      
    }


    /// <summary>
    /// 查询在线磁盘中，最早的500条记录，判断文件是否还存在
    /// </summary>
    private string queryEarliestDataSQL = "SELECT f.* FROM files f INNER JOIN drivers d ON f.volumeNumber = d.driverNumber WHERE d.online = '1' ORDER BY f.scanTime ASC LIMIT 1,500;";

    

    private string deleteFileSQL = "DELETE FROM files WHERE fullPath = @fullPath";
    private string updateFileSQL = "UPDATE files SET fileName = @fileName,md5=@md5,volumeNumber=@volumeNumber,rootPath=@rootPath,directory=@directory,isDirectory=@isDirectory,scanTime=@scanTime  WHERE fullPath = @fullPath";
    private string insertFileSQL = "REPLACE INTO files(fileName,md5,volumeNumber,rootPath,directory,fullPath,isDirectory,scanTime) VALUES(@fileName,@md5,@volumeNumber,@rootPath,@directory,@fullPath,@isDirectory,@scanTime)";
    private DbParameter[] ConstructFileSQLParameter(FileInfoEx file) {
      DbParameter[] dbParameters = new SQLiteParameter[]{
            new SQLiteParameter ("@fileName", file.FileName ),
            new SQLiteParameter ("@md5", file.Md5),
            new SQLiteParameter ("@volumeNumber", file.VolumeNumber ),
            new SQLiteParameter ("@rootPath", file.RootPath ),
            new SQLiteParameter ("@directory", file.Directory),
            new SQLiteParameter ("@fullPath", file.FullPath ),
            new SQLiteParameter ("@isDirectory", file.IsDirectory ),
            new SQLiteParameter ("@scanTime", file.ScanTime)
            };
      return dbParameters;
    }



    private string deleteDriverSQL = "UPDATE drivers SET online = 0,scanTimesCount = @scanTimesCount WHERE scanTimesCount != @scanTimesCount";
    private string updateDriverSQL = "UPDATE drivers SET dirverName = @dirverName,scanTime=@scanTime,online=@online,scanTimesCount=@scanTimesCount WHERE driverNumber = @driverNumber";
    private string insertDriverSQL = "REPLACE INTO drivers(driverNumber,dirverName,scanTime,online,scanTimesCount) VALUES(@driverNumber,@dirverName,@scanTime,@online,@scanTimesCount)";
    private DbParameter[] ConstructDriverSQLParameter(DriveInfo driverInfo) {
      DbParameter[] dbParameters = new SQLiteParameter[]{
            new SQLiteParameter ("@driverNumber", DiskDriver.GetHardDiskNumberByVolume(driverInfo.Name.Trim('\\').Trim(':')) ),
            new SQLiteParameter ("@dirverName", driverInfo.Name ),
            new SQLiteParameter ("@scanTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")),
            new SQLiteParameter ("@online", 1),
            new SQLiteParameter ("@scanTimesCount", scanTimesCount)
            };
      return dbParameters;
    }



    /// <summary>
    /// 如果当前路径是系统目录，或者隐藏目录，不进行更新操作
    /// </summary>
    /// <param name="fullPath"></param>
    /// <returns></returns>
    private bool isSystem(string fullPath) {
      bool ret = false;
      DirectoryInfo directory = new DirectoryInfo(Path.GetDirectoryName(fullPath));
      //如果当前路径的父目录，等于根据录，不管是不是系统文件，都要更新；如果当前文件父目录不是根目录，但是系统目录，就跳过不做处理
      if(Path.GetPathRoot(fullPath) != directory.FullName) {
        if((directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden || (directory.Attributes & FileAttributes.System) == FileAttributes.System) {//系统和隐藏的文件夹
          ret = true;
        }
      }
      return ret;
    }

  }

}
